package com.tang.common.resources;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.tang.common.enumtype.PolicyType;
import com.tang.common.properties.MinioProperties;
import com.tang.common.resources.OssTemplate;
import com.tang.module.system.entity.OssFile;
import com.tang.module.system.entity.TaoFile;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.InputStream;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @author tang
 * @date 2022/1/20 14:32
 */
@Component
@ConditionalOnProperty(prefix = "minio",name = "enable",havingValue = "true")
public class MinioTemplate implements OssTemplate {


    @Resource
    private MinioClient client;


    @Resource
    private MinioProperties minioProperties;



    @Override
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!client.bucketExists(BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build())) {
            //创建桶
            client.makeBucket(MakeBucketArgs.builder().bucket(getBucketName(bucketName)).build());
            //设置桶策略，只读，只写，读写,控制外界是否可以直接通过url访问该桶内的资源
            client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(getBucketName(bucketName)).config(getPolicyType(getBucketName(bucketName), PolicyType.READ)).build());
        }
    }

    @SneakyThrows
    public Bucket getBucket() {
        return getBucket(getBucketName());
    }

    @SneakyThrows
    public Bucket getBucket(String bucketName) {
        Optional<Bucket> bucketOptional = client.listBuckets().stream().filter(bucket -> bucket.name().equals(getBucketName(bucketName))).findFirst();
        return bucketOptional.orElse(null);
    }

    @SneakyThrows
    public List<Bucket> listBuckets() {
        return client.listBuckets();
    }

    @Override
    @SneakyThrows
    public void removeBucket(String bucketName) {
        client.removeBucket(RemoveBucketArgs.builder().bucket(getBucketName(bucketName)).build());
    }

    @Override
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return client.bucketExists(BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build());
    }

    @Override
    @SneakyThrows
    public void copyFile(String bucketName, String fileName, String destBucketName) {
        copyFile(bucketName, fileName, destBucketName, fileName);
    }

    @Override
    @SneakyThrows
    public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
        client.copyObject(
                CopyObjectArgs.builder()
                        //源
                        .source(CopySource.builder().bucket(getBucketName(bucketName)).object(fileName).build())
                        //目标
                        .bucket(getBucketName(destBucketName))
                        .object(destFileName)
                        .build()
        );
    }

    @Override
    @SneakyThrows
    public OssFile statFile(String fileName) {
        return statFile(minioProperties.getName(), fileName);
    }

    @Override
    @SneakyThrows
    public OssFile statFile(String bucketName, String fileName) {
        StatObjectResponse stat = client.statObject(
                StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
        );
        OssFile ossFile = new OssFile();
        ossFile.setName(StringUtils.isEmpty(stat.object()) ? fileName : stat.object());
        ossFile.setLink(fileLink(ossFile.getName()));
        ossFile.setHash(String.valueOf(stat.hashCode()));
        ossFile.setLength(stat.size());
        ossFile.setPutTime(Date.from(stat.lastModified().toLocalDateTime().atZone( ZoneId.systemDefault()).toInstant()));
        ossFile.setContentType(stat.contentType());
        return ossFile;
    }

    @Override
    public String filePath(String fileName) {
        return getBucketName().concat(StringPool.SLASH).concat(fileName);
    }

    @Override
    public String filePath(String bucketName, String fileName) {
        return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
    }

    @Override
    @SneakyThrows
    public String fileLink(String fileName) {
        return minioProperties.getUrl().concat(StringPool.SLASH).concat(getBucketName()).concat(StringPool.SLASH).concat(fileName);
    }

    @Override
    @SneakyThrows
    public String fileLink(String bucketName, String fileName) {
        return minioProperties.getUrl().concat(StringPool.SLASH).concat(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
    }

    @Override
    @SneakyThrows
    public TaoFile putFile(MultipartFile file) {
        return putFile(minioProperties.getName(), file.getOriginalFilename(), file);
    }

    @Override
    @SneakyThrows
    public TaoFile putFile(String fileName, MultipartFile file) {
        return putFile(minioProperties.getName(), fileName, file);
    }

    @Override
    @SneakyThrows
    public TaoFile putFile(String bucketName, String fileName, MultipartFile file) {
        return putFile(bucketName, file.getOriginalFilename(), file.getInputStream());
    }

    @Override
    @SneakyThrows
    public TaoFile putFile(String fileName, InputStream stream) {
        return putFile(minioProperties.getName(), fileName, stream);
    }

    @Override
    @SneakyThrows
    public TaoFile putFile(String bucketName, String fileName, InputStream stream) {
        return putFile(bucketName, fileName, stream, "application/octet-stream");
    }

    @SneakyThrows
    public TaoFile putFile(String bucketName, String fileName, InputStream stream, String contentType) {
        makeBucket(bucketName);
        String originalName = fileName;
        fileName = getFileName(fileName);
        client.putObject(
                PutObjectArgs.builder()
                        .bucket(getBucketName(bucketName))
                        .object(fileName)
                        .stream(stream, stream.available(), -1)
                        .contentType(contentType)
                        .build()
        );
        TaoFile file = new TaoFile();
        file.setOriginalName(originalName);
        file.setName(fileName);
        file.setDomain(getOssHost(bucketName));
        file.setLink(fileLink(bucketName, fileName));
        return file;
    }

    @Override
    @SneakyThrows
    public void removeFile(String fileName) {
        removeFile(minioProperties.getName(), fileName);
    }

    @Override
    @SneakyThrows
    public void removeFile(String bucketName, String fileName) {
        client.removeObject(
                RemoveObjectArgs.builder().bucket(getBucketName(bucketName)).object(fileName).build()
        );
    }

    @Override
    @SneakyThrows
    public void removeFiles(List<String> fileNames) {
        removeFiles(minioProperties.getName(), fileNames);
    }

    @Override
    @SneakyThrows
    public void removeFiles(String bucketName, List<String> fileNames) {
        Stream<DeleteObject> stream = fileNames.stream().map(DeleteObject::new);
        client.removeObjects(RemoveObjectsArgs.builder().bucket(getBucketName(bucketName)).objects(stream::iterator).build());
    }

    /**
     * 根据规则生成存储桶名称规则
     *
     * @return String
     */
    private String getBucketName() {
        return getBucketName(minioProperties.getName());
    }

    /**
     * 根据规则生成存储桶名称规则
     *
     * @param bucketName 存储桶名称
     * @return String
     */
    private String getBucketName(String bucketName) {
        return bucketName;
    }

    /**
     * 根据规则生成文件名称规则
     *
     * @param originalFilename 原始文件名
     * @return string
     */
    private String getFileName(String originalFilename) {
        return fileName(originalFilename);
    }

    /**
     * 获取文件外链
     *
     * @param bucketName bucket名称
     * @param fileName   文件名称
     * @param expires    过期时间 <=7 秒级
     * @return url
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String fileName, Integer expires) {
        return client.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(getBucketName(bucketName))
                        .object(fileName)
                        .expiry(expires)
                        .build()
        );
    }

    /**
     * 获取存储桶策略
     *
     * @param policyType 策略枚举
     * @return String
     */
    public String getPolicyType(PolicyType policyType) {
        return getPolicyType(getBucketName(), policyType);
    }

    /**
     * 获取存储桶策略
     *
     * @param bucketName 存储桶名称
     * @param policyType 策略枚举
     * @return String
     */
    public static String getPolicyType(String bucketName, PolicyType policyType) {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n");
        builder.append("    \"Statement\": [\n");
        builder.append("        {\n");
        builder.append("            \"Action\": [\n");

        switch (policyType) {
            case WRITE:
                builder.append("                \"s3:GetBucketLocation\",\n");
                builder.append("                \"s3:ListBucketMultipartUploads\"\n");
                break;
            case READ_WRITE:
                builder.append("                \"s3:GetBucketLocation\",\n");
                builder.append("                \"s3:ListBucket\",\n");
                builder.append("                \"s3:ListBucketMultipartUploads\"\n");
                break;
            default:
                builder.append("                \"s3:GetBucketLocation\"\n");
                break;
        }

        builder.append("            ],\n");
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("\"\n");
        builder.append("        },\n");
        if (PolicyType.READ.equals(policyType)) {
            builder.append("        {\n");
            builder.append("            \"Action\": [\n");
            builder.append("                \"s3:ListBucket\"\n");
            builder.append("            ],\n");
            builder.append("            \"Effect\": \"Deny\",\n");
            builder.append("            \"Principal\": \"*\",\n");
            builder.append("            \"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n");
            builder.append("        },\n");

        }
        builder.append("        {\n");
        builder.append("            \"Action\": ");

        switch (policyType) {
            case WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            case READ_WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:GetObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            default:
                builder.append("\"s3:GetObject\",\n");
                break;
        }

        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("/*\"\n");
        builder.append("        }\n");
        builder.append("    ],\n");
        builder.append("    \"Version\": \"2012-10-17\"\n");
        builder.append("}\n");
        return builder.toString();
    }

    /**
     * 获取域名
     *
     * @param bucketName 存储桶名称
     * @return String
     */
    public String getOssHost(String bucketName) {
        return minioProperties.getUrl() + StringPool.SLASH + getBucketName(bucketName);
    }
    /**
     * 获取域名
     *
     * @return String
     */
    public String getOssHost() {
        return getOssHost(minioProperties.getName());
    }

    public String fileName(String originalFilename) {
        String[] split = originalFilename.split("\\.");
        return "upload" + StringPool.SLASH + DateUtil.today()
                + StringPool.SLASH + IdUtil.simpleUUID()
                + StringPool.DOT + split[1];
    }
}
